using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Affine is an algorithm to apply an Affine Transformation on an image
    /// It's not very efficient, and assumes that the output array has the
    /// same size as the input array. Efficiency could be much improved by iterating
    /// over all output pixels, back projecting to the corresponding input pixel
    /// and interpolating to overcome aliasing.
    /// </summary>
    public class AffineTransformation : BaseImageFilter
    {
        //the width and height of the output image
        private int m_nWidth = -1;
        private int m_nHeight = -1;
        private int[] m_aDest1d = null;

        /// <summary>
        /// Constructs a new Affine operator
        /// </summary>
        public AffineTransformation()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AffineTransformation"/> class.
        /// </summary>
        /// <param name="_affineTransformation">The _affine transformation.</param>
        internal AffineTransformation(AffineTransformation _affineTransformation)
        {
        }

        /// <summary>
        /// Executes the filter operation
        /// </summary>
        /// <param name="_bitmap"></param>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            UnsafeBitmap bitmap = _bitmap;

            m_nWidth = bitmap.Width;
            m_nHeight = bitmap.Height;

            float[] aArray1 = new float[] { 0, 1, 1, 0 };
            float[] aArray2 = new float[] { 0, 1 };
            int[] dest = AffineTransform(bitmap, _bitmap.Width, _bitmap.Height, aArray1, aArray2);

            int i = 0;
            int y = 0;
            int x = 0;

            Color color = Color.Transparent;

            for (y = 0; y < m_nHeight; y++)
            {
                for (x = 0; x < m_nWidth; x++)
                {
                    color = Color.FromArgb(dest[i++]);
                    bitmap.SetPixel(x, y, color);
                }
            }
            return _bitmap;
        }

        /// <summary>
        /// Applies the Affine operator on the specified image array
        /// affine transformation algorithm..
        /// a) assume the image is grey level (hence RR=GG=BB)
        /// b) use value 0x000000ff to get the BB value
        /// c) split each pixel into 100 subpixels
        /// d) apply the operation (eg Affine)
        /// e) create int value 0xffCDCDCD
        /// </summary>
        /// <param name="_bitmap">The first source image as a pixel array</param>
        /// <param name="_nWidth">Width of the _n.</param>
        /// <param name="_nHeight">Height of the _n.</param>
        /// <param name="a_array">2x2 array used to find the transformation</param>
        /// <param name="b_array">1x1 array used to find the transformation</param>
        /// <returns>
        /// A pixel array containing the transformed image
        /// </returns>
        public int[] AffineTransform(UnsafeBitmap _bitmap, int _nWidth, int _nHeight,
                       float[] a_array, float[] b_array)
        {

            //The output array
            m_nWidth = _nWidth;
            m_nHeight = _nHeight;
            m_aDest1d = new int[m_nWidth * m_nHeight];

            //Used to divide each pixel into 100
            int j = 0;
            int k = 0;

            float fTmpj = 0;
            float fTmpk = 0;

            //Local variables

            int[] tmp_1d = new int[m_nWidth * m_nHeight];
            int outpos = 0;
            int x_pos = 0;
            int y_pos = 0;
            int nSrcRGB;
            float fXAff;
            float fYAff;
            int nXOut;
            int nYOut;
            float nXTmp;
            float nYTmp;
            int nResult;

            //Used to count how many subpixels hit each output pixel
            int[] aCount = new int[m_nWidth * m_nHeight];

            //initialise the two arrays used for the computation

            for (int y = 0; y < m_aDest1d.Length; y++)
            {
                tmp_1d[y] = 0x00000000;
                m_aDest1d[y] = 0x00000000;
                aCount[y] = 0;
            }

            //Now update the two arrays
            for (int i = 0; i < m_aDest1d.Length; i++)
            {
                y_pos = find_y(i, m_nWidth);
                x_pos = (i - (y_pos * m_nWidth));
                nSrcRGB = _bitmap[i] & 0x000000ff;
                for (j = 0; j < 10; j++)
                {
                    for (k = 0; k < 10; k++)
                    {
                        //Work out the sub pixel postion
                        fTmpj = j;
                        fTmpk = k;
                        nXTmp = x_pos + (fTmpj / 10);
                        nYTmp = y_pos + (fTmpk / 10);

                        //Multiply by the transformation matrices
                        fXAff = (((a_array[0] * nXTmp) + (a_array[1] * nYTmp)) + (b_array[0]));
                        fYAff = (((a_array[2] * nXTmp) + (a_array[3] * nYTmp)) + (b_array[1]));

                        //Convert back to integer
                        nXOut = (int)Math.Floor(fXAff);
                        nYOut = (int)Math.Floor(fYAff);

                        //Check if the answer is outside image range
                        if (nXOut > (m_nWidth - 1) || nXOut < 0 || nYOut > (m_nHeight - 1) || nYOut < 0)
                        {
                            continue;
                        }
                        else
                        {
                            //Find position in output array
                            outpos = (nXOut + (m_nWidth * nYOut));

                            //Increment arrays
                            tmp_1d[outpos] = (tmp_1d[outpos] + nSrcRGB);
                            aCount[outpos]++;
                        }
                    }
                }
            }

            //Now scale down the destination arrays by the number of hits
            for (int m = 0; m < m_aDest1d.Length; m++)
            {
                if (aCount[m] == 0)
                {
                    //set to black
                    nResult = 0;
                }
                else
                {
                    nResult = (tmp_1d[m] / aCount[m]);

                    //clip values
                    if (nResult > 255)
                    {
                        nResult = 255;
                    }
                    else if (nResult < 0)
                    {
                        nResult = 0;
                    }
                }
                m_aDest1d[m] = (int)(0xFF << 24 | (nResult + (nResult << 16) + (nResult << 8)));
            }
            return m_aDest1d;
        }

        /// <summary>
        /// Used to find the y position of the current point
        /// </summary>
        /// <param name="_nOffset">The _n offset.</param>
        /// <param name="_nWidth">Width of the _n.</param>
        /// <returns></returns>
        private int find_y(int _nOffset, int _nWidth)
        {
            int nAns = 0;
            while (_nOffset >= _nWidth)
            {
                nAns++;
                _nOffset = _nOffset - _nWidth;
            }
            return nAns;
        }

        /// <summary>
        /// Clones this filter
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new AffineTransformation(this);
        }
    }
}